package database

import exceptions.*
import result.BottleData
import result.HttpResult
import result.NormalPickResult
import tools.BotMgr
import tools.ContentCheck
import java.util.*
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock

fun unixTime(): Int = (System.currentTimeMillis() / 1000).toInt()

object BottlePool {
    private val LOCKS = mutableMapOf<Long, Lock>()

    /**
     * 捞起一个漂流瓶，含异常的应对
     */
    @JvmStatic
    fun pick(user: User, groupId: Long, botId: Long): HttpResult {
        if (!LOCKS.keys.contains(user.userId)) {
            LOCKS[user.userId] = ReentrantLock()
        }
        val lock = LOCKS[user.userId]
        lock?.lock()

        try {
            val bottle = pick(user, groupId)
            return NormalPickResult(
                code = 200,
                time = unixTime(),
                message = "OK",
                data = BottleData(
                    added = bottle.added,
                    botName = BotMgr.getName(bottle.botId),
                    color = userIdToColor(bottle.userId),
                    place = groupIdToPlace(bottle.groupId),
                    content = bottle.content
                )
            )
        } catch (e: BaseException) {
            return HttpResult(
                code = e.code,
                time = unixTime(),
                message = "" + e.message
            )
        } finally {
            lock?.unlock()
        }
    }

    /**
     * 捞取一个漂流瓶
     */
    // 这个是封装起来的，会抛出异常，不会把异常转化成 PickResult
    @JvmStatic
    private fun pick(user: User, groupId: Long): Bottle {
        if (!user.isAvailable()) {
            throw TooManyPickException()
        }
        val conn = DBConnMgr.getConn()
        val query =
            conn.prepareStatement("SELECT * FROM table_bottles WHERE `usage`>0 AND `group_id`!=? AND `user_id`!=? ORDER BY (`id`*1453 + `added`)%? LIMIT 1;")
                .apply {
                    setLong(1, if (groupId != -1L) groupId else -2L) //私聊无限制
                    setLong(2, user.userId)
                    setInt(3, arrayOf(5, 7, 11, 13, 17, 19, 23, 29).random())
                    executeQuery()
                }
        val resultSet = query.resultSet
        while (resultSet.next()) {
            /* usage 减一 */
            conn.prepareStatement("UPDATE table_bottles SET `usage`=`usage`-1 WHERE id=?;").apply {
                setInt(1, resultSet.getInt("id"))
                executeUpdate()
                close()
            }

            /* 用户信息更改 */
            conn.prepareStatement("UPDATE table_users SET `count_pick`=`count_pick`+1 WHERE user_id=?;").apply {
                setLong(1, user.userId)
                executeUpdate()
                close()
            }

            val bottle = Bottle(
                id = resultSet.getInt("id"),
                added = resultSet.getInt("added"),
                botId = resultSet.getLong("bot_id"),
                userId = resultSet.getLong("user_id"),
                groupId = resultSet.getLong("group_id"),
                content = resultSet.getString("content"),
                usage = resultSet.getInt("usage")
            )
            resultSet.close()
            query.close()
            println(bottle)
            return bottle
        }
        throw NoBottleException()
    }

    /**
     * 投递一个漂流瓶
     */
    @JvmStatic
    fun send(user: User, groupId: Long, botId: Long, content: String) {
        if (countLastBottles(user) > 3) {
            throw FrequentSendException()
        }
        if (!ContentCheck.check(content)) {
            throw IllegalContentException()
        }
        val conn = DBConnMgr.getConn()
        conn.prepareStatement("INSERT INTO table_bottles (added, bot_id, user_id, group_id, content, `usage`) VALUES (UNIX_TIMESTAMP(), ?, ?, ?, ?, 3)")
            .apply {
                setLong(1, botId)
                setLong(2, user.userId)
                setLong(3, groupId)
                setString(4, content)
                executeUpdate()
                close()
            }
        /* 用户信息更改 */
        conn.prepareStatement("UPDATE table_users SET `count_send`=`count_send`+1 WHERE user_id=?;").apply {
            setLong(1, user.userId)
            executeUpdate()
            close()
        }
    }

    /**
     * 返回指定用户在近 30 分钟投递的漂流瓶数目
     */
    @JvmStatic
    fun countLastBottles(user: User): Int {
        val conn = DBConnMgr.getConn()
        val query =
            conn.prepareStatement("SELECT COUNT(0) AS `cnt` FROM table_bottles WHERE user_id=? AND added>UNIX_TIMESTAMP()-1800;")
                .apply {
                    setLong(1, user.userId)
                    executeQuery()
                }
        val resultSet = query.resultSet
        while (resultSet.next()) {
            return resultSet.getInt("cnt")
        }
        return 0
    }

    @JvmStatic
    fun rank(): String {
        val conn = DBConnMgr.getConn()
        val sendQuery =
            conn.prepareStatement("SELECT `group_id`,count(0) AS `cnt` FROM table_bottles WHERE group_id>0 AND added>UNIX_TIMESTAMP()-604800 GROUP BY`group_id` ORDER BY `cnt` DESC LIMIT 5;")
                .apply {
                    executeQuery()
                }
        val sendSet = sendQuery.resultSet
        val stringBuilder = StringBuilder()
        while (sendSet.next()) {
            stringBuilder.append(groupIdToPlace(sendSet.getLong("group_id")))
                .append(" (").append((sendSet.getInt("cnt"))).append(")\n")
        }
        sendQuery.close()
        sendSet.close()
        return if (stringBuilder.isNotEmpty())
            stringBuilder.substring(startIndex = 0, endIndex = stringBuilder.length - 1)
        else
            "近一周没有任何漂流瓶投递~"
    }


    private val COLORS = ("黄灰 海蓝 老绿 石榴红 米灰 奶绿 柠檬黄 甘草黄 钴蓝 百草霜 宝蓝 苍青 金黄 品红 橘红 孔雀蓝 花青 翠绿" +
            " 姜黄 藏蓝 碧绿 纯白 淡蓝 鲜红 桃红 天依蓝 米白 青灰 墨黑 胭脂红 罗兰紫 牵牛紫 釉蓝 石绿 铜绿 透明 五颜六色").split(" ")
    private val PLACES = "小溪 竹林 松间 湖畔 石林 云海 农庄 山寨 峡谷 山崖 宫阙 庙宇 扁舟 草原 古屋 村舍 边关 哨站 驿站 酒楼 断桥 枯井".split(" ")

    /**
     * 根据群号返回颜色
     */
    private fun groupIdToPlace(groupId: Long): String = PLACES[((groupId + 10) % PLACES.size).toInt()]

    /**
     * 根据QQ号和日期返回地点
     */
    private fun userIdToColor(userId: Long): String = COLORS[((userId + 10 + 17 * day()) % COLORS.size).toInt()]

    private fun day(): Int = Calendar.getInstance().get(Calendar.DAY_OF_YEAR)

}